package see.battle;

import java.util.concurrent.Callable;
import see.battle.gui.Game.ResultProcessor;
import see.battle.object.ObjectPool;

/* 
 * This class gets messages from server and updates everything.
 */
public class LifeCycle implements Runnable {

  private long _currentTime = System.currentTimeMillis();
  private long _nextTime = System.currentTimeMillis();
  private long _nextShipTime = Const.noTime;
  private long _nextBlastTime = Const.noTime;
  private long _finishingGameTime = Const.noTime;
  private boolean _mustEnterBlast = false;  
  private boolean _mustLeaveBlast = false;
  private boolean _inBlast = false;
  private boolean _stop = false;
  private boolean _gameIsOn = false;
  private ObjectPool _pool;
  private Client _client;
  private ResultProcessor _resultProcessor;

  public class StartGameProcessor implements Callable<Object> {
    
    long _startGameTime = Const.noTime;
    long _seed = 0;
    
    public void set_startGameTime(long startGameTime) {
      _startGameTime = startGameTime;
    }

    public void set_seed(long seed) {
      _seed = seed;
    }

    //@Override
    public Object call() throws Exception {
      startGame(_startGameTime, _seed);
      return null;
    }
  }

  public class BulletProcessor implements Callable<Object> {
    long _time = -1;
    float _position = 0;
    boolean _bulletIsMine = true;
    long _additionalShipDelay = 0;

    public void setTime(long time) {
      _time = time;
    }

    public void setPosition(float position) {
      _position = position;
    }

    public void setBulletIsMine(boolean bulletIsMine) {
      _bulletIsMine = bulletIsMine;
    }
    
    public void setAdditionalShipDelay(long additionalShipDelay) {
      _additionalShipDelay = additionalShipDelay;
    }

    //@Override
    public Object call() throws Exception {
      processBullet(_time, _position, _bulletIsMine, _additionalShipDelay);
      return null;
    }
  }
  
  public class OpponentFocusProcessor implements Callable<Object> {
    
    float _focus;
    
    public void set_focus(float focus) {
      _focus = focus;
    }

    //@Override
    public Object call() throws Exception {
      changeOpponentFocus(_focus);
      return null;
    }
  }

  /**
   * @param pool
   * @param client
   */
  public LifeCycle(ObjectPool pool, Client client) {
    _pool = pool;
    _client = client;
    _pool.set_bulletProcesor(new BulletProcessor());
    _client.set_bulletProcesor(new BulletProcessor());
    _client.set_startGameProcessor(new StartGameProcessor());
    _client.set_opponentFocusProcessor(new OpponentFocusProcessor());
  }

  public void set_resultProcessor(ResultProcessor resultProcessor) {
    _resultProcessor = resultProcessor;
  }

  public boolean is_inBlast() {
    return _inBlast;
  }

  private void processBullet(long time, float position, 
      boolean bulletIsMine, long additionalShipDelay) {
    long tillHitDelay = (long)(((float)1.0 - Const.backgroundSeaLevel) 
        / Const.bulletSpeed * Const.updateDelay);
    _pool.addBullet(position, time, bulletIsMine, _currentTime);
    if (_pool.noMoreBullets()) {
      _finishingGameTime = Math.max(time + tillHitDelay, 
          _nextBlastTime + Const.blastDuration);
    }
    if (bulletIsMine) {
      _client.sendBulletMessage(time, position, additionalShipDelay);
    }
    if (_nextBlastTime != -1) return;
    boolean hit = additionalShipDelay >= 0;
    if (hit) {
      _nextBlastTime = time + tillHitDelay;
      _mustEnterBlast = true;
      _nextShipTime = _nextBlastTime + Const.blastDuration + 
      additionalShipDelay; 
      _pool.set_nextHitIsMine(bulletIsMine);
    }
    if (_pool.noMoreBullets()) {
      _finishingGameTime = time + tillHitDelay;
      if (hit) {
        _finishingGameTime += Const.blastDuration;
      }
    }
  }

  private void startGame(long startGameTime, long seed) {
    _nextShipTime = startGameTime;
    _gameIsOn = true;
    _nextTime = _nextShipTime;
    _pool.initShipsRandom(seed);
  }

  private void changeOpponentFocus(float focus) {
    _pool.changeOpponentFocus(focus);
  }
  
  private void tryToFinish() throws Exception {
    if (_finishingGameTime != Const.noTime && 
        _currentTime > _finishingGameTime) {
      _gameIsOn = false;
      _stop = true;
      _client.stopAdaptor();
      if (_resultProcessor != null) {
        _resultProcessor.setHitNumber(_pool.get_hitNumber());
        _resultProcessor.setOpponentHitNumber(_pool.get_opponentHitNumber());
        _resultProcessor.call();
      }
    }
  }

  private void tryToAddNewShip() {
    if (_currentTime > _nextShipTime) {
      _pool.addShip(_nextShipTime, _currentTime);
      _nextShipTime += Const.shipDelay;
    }
  }

  private void tryToProceedBlast() {
    if ((_currentTime > _nextBlastTime) && 
        (_currentTime < _nextBlastTime + Const.blastDuration)) {
      if (_mustEnterBlast) { 
        _inBlast = true; 
        _pool.set_inBlast(_inBlast);
        _pool.stopShipsForBlast();
        _mustEnterBlast = false;
        _mustLeaveBlast = true;
        _pool.increaseHitsNumber();
      }
    } else {
      if (_mustLeaveBlast) {
        _inBlast = false;
        _pool.set_inBlast(_inBlast);
        _pool.changeShipsDirection();
        _mustLeaveBlast = false;
        _nextBlastTime = Const.noTime;
      }
    }
    if (_inBlast) {
      _pool.resetBlastBackground();
    }
  }

  //@Override
  @SuppressWarnings("static-access")
  public void run() {
    while (true) {
      if (_stop) break;
      try {
        _currentTime = System.currentTimeMillis();
        if (_currentTime > _nextTime) {
          tryToFinish();
          _client.readMessages();
          if (_gameIsOn) {
            tryToAddNewShip();
            tryToProceedBlast();
            _pool.update(_currentTime);
            _pool.drawScene();;
            _client.sendFocusMessage(_pool.getCurrentFocus());
          }
          _nextTime += Const.updateDelay;
          long time = _nextTime - System.currentTimeMillis();
          try {
            if (time > 0) {
              Thread.currentThread().sleep(time);
            }
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      } 
    }
  }
}
